package com.zbj.homemade.spring.aop;

import com.zbj.homemade.spring.BeanFactory;
import com.zbj.homemade.spring.SimpleBeanFactory;
import com.zbj.homemade.spring.annotation.aop.After;
import com.zbj.homemade.spring.annotation.aop.AfterReturning;
import com.zbj.homemade.spring.annotation.aop.AfterThrowing;
import com.zbj.homemade.spring.annotation.aop.Around;
import com.zbj.homemade.spring.annotation.aop.Aspect;
import com.zbj.homemade.spring.annotation.aop.Before;
import com.zbj.homemade.spring.annotation.aop.PointCut;
import com.zbj.homemade.spring.aop.exception.AopException;
import com.zbj.homemade.spring.aop.interceptor.AdviceMethod;
import com.zbj.homemade.spring.aop.interceptor.AfterInterceptor;
import com.zbj.homemade.spring.aop.interceptor.AfterReturningInterceptor;
import com.zbj.homemade.spring.aop.interceptor.AfterThrowingInterceptor;
import com.zbj.homemade.spring.aop.interceptor.AroundInterceptor;
import com.zbj.homemade.spring.aop.interceptor.BeforeInterceptor;
import com.zbj.homemade.spring.aop.interceptor.MethodInterceptor;
import com.zbj.homemade.spring.aop.proxy.AopProxy;
import com.zbj.homemade.spring.aop.proxy.JdkInvocationHandler;
import com.zbj.homemade.spring.exception.BeansException;
import com.zbj.homemade.spring.lifecycle.AopBeanPostProcessor;
import com.zbj.homemade.spring.lifecycle.BeanFactoryAware;
import com.zbj.homemade.spring.lifecycle.BeanPostProcessor;
import com.zbj.homemade.spring.utils.AnnoUtils;
import com.zbj.homemade.spring.utils.Assert;
import com.zbj.homemade.spring.utils.CollectionsUtil;
import org.apache.commons.lang3.StringUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * |                    .  ’   *   ‘  .
 * |               _6                    "     o0000o
 * |  o00o _    .%                         %. o0000000o
 * | o000000o.:                              :(0000000o
 * | o00000os                                  .000000o
 * |  o0000/     <b>不了不了,改需求真的伤身体</>   .000o
 * |    o0o                                       .0o
 * |     y        <b>我认为源代码,就是SkrSkr</>     .
 * |    y                                           ,
 * |   y          <b>小熊猫保佑,代码永不修改</>        ,
 * |   p                            .                 .
 * |   p                           @@.                .
 * |    y                        ;@@@;               .
 * |    \                       ;@@@@;.@.          ,;S
 * |     、                     .@@@@@@;       ,@@@@@
 * |     @\.            .****...*@@@@;     .@@@@@@@@@@@.
 * |    @@@@\.     . @@@@@@@@@@@@@@@;    .@@@@@@@@@@@@@@@.
 * |   @@@@@@@@@.@@@@@@@@@@@@@@@@;   .@@@@@@@@@@@@@@@@@@@@@.
 * |  @@@@@@..@@@@..@@@@@@..@@@@@ .@@...@@@@@..@@..@@@@@..@@.
 * | @@@@@@@@..@..@@@@@@@@@@@@@;.@@@..@..@@@..@@....@@@..@@@@.
 * |@y@@@@@@@@..@@@@@@@@..@@@@i@@@@..@@@..@..@@..@@..@..@@@@n@.
 * |@n@@@@@@@..@@@@@@@@..@@@@@@@@@..@@@@@...@@..@@@@...@@@@@n@@.
 * |@@@@@@@@..@@@@@@@@..@@@@@@@@@..@@@@@@@.@@..@@@@@..@@@@@@@@@@.
 * |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@.
 *
 * @program: principle
 * @description: 代理对象创建器
 * <p>
 * ps：{spring在这里为代理单独定制了后置处理器接口
 * ★SmartInstantiationAwareBeanPostProcessor -》 InstantiationAwareBeanPostProcessor -》BeanPostProcessor}
 * <p>
 * 在实例化前后,为了简单 这里使用只用初始化前后
 * @author: Yinn
 * @create: 2019-03-06 14:01
 **/
public class AutoProxyCreator implements BeanPostProcessor, BeanFactoryAware {

    private SimpleBeanFactory beanFactory;

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        /**
         * 解析切面类{@link Aspect,Before,After,Around,AfterReturning,AfterThrowing}
         */
        if (AspectChainFatory.resolveAspect(bean.getClass(), bean)){
            System.out.println(bean.getClass().getName() + " is a Aspect!" );
        }
        return bean;
    }

    /**
     * 构建代理对象
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {

        Class cls = bean.getClass();

        //目前只 jdk动态代理
        if (cls.getInterfaces() == null || cls.getInterfaces().length == 0)
            return bean;

        Map<Method, List<MethodInterceptor>> intereptors = AspectChainFatory.matchMethodInterceptors(bean);

        if (intereptors != null && intereptors.size() > 0){
            bean = createProxy(bean, intereptors);
        }

        return bean;
    }

    private Object createProxy(Object bean, Map<Method, List<MethodInterceptor>> intereptors){

        AopProxy proxy = new JdkInvocationHandler(bean, intereptors);

        return proxy.getProxy();

    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) {

        if (beanFactory instanceof SimpleBeanFactory){
            this.beanFactory = (SimpleBeanFactory) beanFactory;
        }

    }


    /**
     * 切点缓存类
     */
    private static class AspectChainFatory {

        /**
         * 程序共用一套切点,逐个校验,切点集合
         */
        private static final List<IPointCut> pointCutCollection = new ArrayList<>();

        /**
         * 拦截链集合
         */
        private static final Map<IPointCut, List<MethodInterceptor>> interceptors = new HashMap<>();

        /**
         * 处理切面类
         *
         * @return :是否是切面类
         */
        public static boolean resolveAspect(Class<?> clz, Object aspectBean) {
            if (AnnoUtils.isClazzAnnotated(clz, Aspect.class)) {

                List<IPointCut> pointCuts = new ArrayList<>();

                //遍历切点
                for (Method method : clz.getDeclaredMethods()) {
                    if (AnnoUtils.isMethodAnnotated(method, PointCut.class)) {
                        pointCuts.add(new PointCutImpl(method.getAnnotation(PointCut.class), method.getName()));
                    }
                }
                pointCutCollection.addAll(pointCuts);

                if (CollectionsUtil.isEmpty(pointCuts))
                    throw new AopException("类" + clz.getName() + " 中不包含任何切点! 请检查！");

                //遍历各种aop注解 并绑定切点与 拦截链的关系
                for (Method method : clz.getDeclaredMethods()) {
                    if (AnnoUtils.isMethodAnnotated(method, Before.class)) {
                        bindInterceptors(pointCuts, method, method.getAnnotation(Before.class),aspectBean);
                    } else if (AnnoUtils.isMethodAnnotated(method, After.class)) {
                        bindInterceptors(pointCuts, method, method.getAnnotation(After.class),aspectBean);
                    } else if (AnnoUtils.isMethodAnnotated(method, AfterReturning.class)) {
                        bindInterceptors(pointCuts, method, method.getAnnotation(AfterReturning.class),aspectBean);
                    } else if (AnnoUtils.isMethodAnnotated(method, AfterThrowing.class)) {
                        bindInterceptors(pointCuts, method, method.getAnnotation(AfterThrowing.class),aspectBean);
                    } else if (AnnoUtils.isMethodAnnotated(method, Around.class)) {
                        bindInterceptors(pointCuts, method, method.getAnnotation(Around.class),aspectBean);
                    }
                }

                return true;
            }

            return false;
        }

        /**
         * 绑定切点 与 拦截链的 映射关系
         */
        private static void bindInterceptors(List<IPointCut> pointCuts, Method cutMethod, Annotation a, Object aspectBean) throws AopException {

            Assert.notNull(cutMethod, "被切方法不可为空！");

            if (a == null)
                return;

            //切点表达式
            String pointCutExpression;
            if (a instanceof Before) {
                pointCutExpression = ((Before) a).pointCut();
            } else if (a instanceof After) {
                pointCutExpression = ((After) a).pointCut();
            } else if (a instanceof AfterReturning) {
                pointCutExpression = ((AfterReturning) a).pointCut();
            } else if (a instanceof AfterThrowing) {
                pointCutExpression = ((AfterThrowing) a).pointCut();
            } else if (a instanceof Around) {
                pointCutExpression = ((Around) a).pointCut();
            } else {
                throw new AopException("注解：" + a + "解析错误");
            }

            if (StringUtils.isBlank(pointCutExpression))
                return;
            for (IPointCut pointCut : pointCuts) {
                if (pointCut.contains(pointCutExpression)) {
                    List<MethodInterceptor> methodInterceptors = interceptors.get(pointCut);

                    if (methodInterceptors == null) {
                        methodInterceptors = new ArrayList<>();
                        interceptors.put(pointCut, methodInterceptors);
                    }

                    //构建方法攔截器
                    MethodInterceptor methodInterceptor;
                    if (a instanceof Before) {
                        methodInterceptor = new BeforeInterceptor(new AdviceMethod(cutMethod, AdviceMethod.BEFORE, aspectBean));
                    } else if (a instanceof After) {
                        methodInterceptor = new AfterInterceptor(new AdviceMethod(cutMethod, AdviceMethod.AFTER, aspectBean));
                    } else if (a instanceof AfterReturning) {
                        methodInterceptor = new AfterReturningInterceptor(new AdviceMethod(cutMethod, AdviceMethod.AFTERRETURNING,aspectBean));
                    } else if (a instanceof AfterThrowing) {
                        methodInterceptor = new AfterThrowingInterceptor(new AdviceMethod(cutMethod, AdviceMethod.AFTERTHROWING,aspectBean));
                    } else if (a instanceof Around) {
                        methodInterceptor = new AroundInterceptor(new AdviceMethod(cutMethod, AdviceMethod.AROUND,aspectBean));
                    } else {
                        throw new AopException("注解：" + a + "解析错误");
                    }

                    //注册
                    methodInterceptors.add(methodInterceptor);
                }
            }
        }


        /**
         * 当前方法是否被切到
         */
        public static boolean isCut(Method method) {

            boolean flag = false;

            if (pointCutCollection.size() == 0)
                return flag;


            for (IPointCut pointCut : pointCutCollection) {
                if (pointCut.isMatch(method)) {
                    flag = true;
                    break;
                }
            }

            return flag;
        }

        /**
         * 获取切点集合
         */
        public static List<IPointCut> getPointCuts(Method method) {
            List<IPointCut> pointCuts = new ArrayList<>();

            for (IPointCut pointCut : pointCutCollection) {
                if (pointCut.isMatch(method)) {
                    pointCuts.add(pointCut);
                }
            }

            return pointCuts;
        }

        /**
         * 获取拦截链
         */
        public static List<MethodInterceptor> getChain(Method method) {

            List<IPointCut> pointCuts = getPointCuts(method);

            if (pointCuts == null || pointCuts.size() < 1)
                return null;

            List<MethodInterceptor> result = new ArrayList<>();

            for (IPointCut iPointCut : pointCuts) {
                List<MethodInterceptor> interceptorList = interceptors.get(iPointCut);

                if (interceptorList != null && interceptorList.size() < 1)
                    continue;

                result.addAll(interceptorList);
            }

            //对拦截链结果集排序
            result.sort(new Comparator<MethodInterceptor>() {
                @Override
                public int compare(MethodInterceptor o1, MethodInterceptor o2) {
                    int t1 = o1.getAdviceType();
                    int t2 = o2.getAdviceType();
                    if (t1 > t2)
                        return -1;
                    else if (t1 == t2)
                        return 0;
                    else
                        return 1;
                }
            });

            return result;
        }

        public final static Map<Method, List<MethodInterceptor>> matchMethodInterceptors(Object bean) {

            if (bean == null)
                Assert.notNull(bean, "bean为空！");

            Class<?> cls = bean.getClass();

            Map<Method, List<MethodInterceptor>> result = new HashMap<>();
            for (Method method : cls.getDeclaredMethods()) {

                List<MethodInterceptor> methodInterceptors = getChain(method);

                if (!CollectionsUtil.isEmpty(methodInterceptors))
                    result.put(method, methodInterceptors);
            }

            return result;
        }
    }
}
